<?php
/**
 * Defines generic lexer classes.
 *
 * Here all code that related to lexer.
 *
 * @package    block
 * @subpackage formal_langs
 * @copyright &copy; 2011 Sergey Pashaev, Volgograd State Technical University
 * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License
 */

global $CFG;
require_once($CFG->dirroot . '/blocks/formal_langs/tokens_base.php');
require_once($CFG->dirroot . '/blocks/formal_langs/lexical_pattern.php');
require_once($CFG->dirroot . '/blocks/formal_langs/langs_code/system/lex_pattern_lexer.php');
require_once($CFG->dirroot . '/blocks/formal_langs/langs_code/system/lex_pattern_parser.php');

/**
 * Lexer class.
 *
 * @copyright  &copy; 2011 Sergey Pashaev, Volgograd State Technical University
 * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License
 */
class block_formal_langs_scaner {

    /**
     * List of pattern class objects.
     * @var array
     */
    private $patterns;

    /**
     * List of conditions. Contains at least one condition: "INITIAL"
     * @var array
     */
    private $conditions;

    /**
     * Options hash table. optionname -> value
     * @var array
     */
    private $options;

    /**
     * Input text buffer.
     * @var string
     */
    private $text;

    /**
     * Current column in input buffer.
     * @var integer
     */
    private $column;

    /**
     * Current line in input buffer.
     * @var integer
     */
    private $line;

    /**
     * Current condition. Initial: "INITIAL".
     * @var string
     */
    private $condition = 'INITIAL';

    /**
     * Array of errors. Maybe array of error class objects.
     * @var array
     */
    private $errors;

    private $token_count;

    private $pattern_buffers;

    public function patterns_count() {
        return count($this->patterns);
    }

    public function patterns_name_list() {
        $result = array();
        foreach ($this->patterns as $p) {
            array_push($result, $p->name());
        }
        return $result;
    }

    public function print_dot($dotfile){
        $fhdot = fopen($dotfile, 'w');
        fwrite($fhdot, "digraph g {\ngraph [rankdir=\"LR\"]; node [fontname=\"Droid Sans\" fontsize=12]\n");

        fwrite($fhdot, "START [shape=\"none\"];\n");
        fwrite($fhdot, "END [shape=\"doublecircle\"];\n");

        fwrite($fhdot, "START->INITIAL\n");
        
        foreach ($this->conditions as $c) {
            $str = sprintf("%s [shape=\"circle\"];\n", $c);
            fwrite($fhdot, $str);
            $str = sprintf("%s->END [label=\"\$EOF\"];\n", $c);
            fwrite($fhdot, $str);
        }

        foreach ($this->patterns as $p) {
            $str = sprintf("%s%d [shape=\"record\" label=\"%s", $p->name(), $p->number(), $p->name());
            if (!is_null($p->actions())) {
                foreach($p->actions() as $a) {
                    $str .= sprintf("|%s", $a->type());
                }
            }
            
            $str .= "\"];\n";
            fwrite($fhdot, $str);
            $regex = $p->regexp();
            $regex = preg_replace("/\"/", "\\\"", $regex);
            $str = sprintf("%s->%s%d [label=\"%s\"]\n", $p->activecondition(), $p->name(), $p->number(), $regex);
            fwrite($fhdot, $str);
            $next = "";
            if ($p->nextcondition() == null) {
                $next = $p->activecondition();
            } else {
                $next = $p->nextcondition();
            }
            $str = sprintf("%s%d->%s\n", $p->name(), $p->number(), $next, $p->regexp());
            fwrite($fhdot, $str);
        }
        
        fwrite($fhdot, "}\n");
        fclose($fhdot);
        $cmd = sprintf("sfdp -T png %s -o %s.png", $dotfile, $dotfile);
        exec($cmd);
    }

    /**
     * Basic lexer constructor.
     *
     * @param array $patterns - array of object of a pattern class
     * @param array $conditions - array of strings/conditions
     * @param string $text - input text
     * @param array $options - hash table of options
     * @param string $condition - initial condition
     */
    public function __construct($lexerrules) {
        $rules_lexer = new lex_pattern_lexer(fopen('data://text/plain;base64,' . base64_encode($lexerrules), 'r'));
        $rules_parser = new lex_pattern_yyParser();

        while ($token = $rules_lexer->next_token()) {
            $rules_parser->doParse($token->type(), $token->value());
        }
        $rules_parser->doParse(0);

        $this->patterns        = $rules_parser->list;
        $this->conditions      = $rules_parser->conditions;
        $this->options         = null;
        $this->condition       = 'INITIAL';
        $this->text            = null;
        $this->token_count     = 0;
        $this->pattern_buffers = null;
    }

    /**
     * This function returns array of tokens which was exracted from
     * input buffer.
     *
     * @return array - array of base_token objects
     */
    public function tokenize($text) {
        // some initializations
        $this->text = $text;
        $this->current_line = 0;
        $this->current_column = 0;
        $this->token_count = 0;
        $this->pattern_buffers = array();

        $max_match_str = "";
        $max_match_length = 0;        /** Length of maximum match. */
        $max_match_pattern = null;    /** Pattern that corresponds to
                                       * the maximum match.*/
        $match_str = '';              /** Matched text.*/
        $matches = array();           /** List of matches for all
                                       * patterns at current condition.*/
        $result = array();            /** Resulting array of tokens.*/

        while (strlen($this->text) > 0 ) {
            // reset max matches
            $max_match_str = "";
            $max_match_length = 0;
            $max_match_pattern = null;

            // loop throug patterns
            foreach ($this->patterns as $p) {
                // use patterns only for current state
                if ($p->activecondition() == $this->condition) {
                    // fix regexp
                    $modified_regexp = "/^" . $p->regexp() . "/";
                    $matches = array();

                    if (preg_match($modified_regexp, $this->text, $matches)) {
                        // match !
                        $match_str = $matches[0];

                        // save max match
                        if (strlen($match_str) > $max_match_length) {
                            $max_match_str = $match_str;
                            $max_match_length = strlen($match_str);
                            $max_match_pattern = $p;
                        }
                    }
                }
            }

            if (null == $max_match_pattern) {
                // no match
                // throw new Exception('Lexer: none pattern matches');
                $this->text = substr($this->text, 1);
                //TODO: cut 1 char and try to continue
            } else {
                // check pattern next condtition
                if ($max_match_pattern->nextcondition() != null) {
                    $this->condition = $max_match_pattern->nextcondition();
                }

                // check pattern actions
                if ($max_match_pattern->actions() != null) {

                    //var_dump($this->pattern_buffers);
                    //check pattern_buffer
                    if(array_key_exists($max_match_pattern->name(), $this->pattern_buffers)) {
                    } else {
                        $this->pattern_buffers[$max_match_pattern->name()] = "";
                    }

                    // do useful work
                    foreach($max_match_pattern->actions() as $a) {
                        switch($a->type()) {

                        case "create" :
                            // get string from buffer and create token
                            // then clean buffer
                            $new_token = new block_formal_langs_token_base(NULL, $max_match_pattern->number(), $this->pattern_buffers[$max_match_pattern->name()], NULL, $this->token_count++);
                            $new_token->set_description($max_match_pattern->name());
                            array_push($result, $new_token);
                            $this->pattern_buffers[$max_match_pattern->name()] = "";
                            break;

                        case "copy" :
                            $this->pattern_buffers[$max_match_pattern->name()] = $max_match_str;
                            break;

                        case "append" :
                            $this->pattern_buffers[$max_match_pattern->name()] .= $max_match_str;
                            break;

                        case "error" :
                            break;
                        }
                    }
                    //TODO: update current line and column
                } else {
                    //TODO: update current line and column
                }
                
                // cut text buffer
                $this->text = substr($this->text, $max_match_length);
            }
        }
        return $result;
    }
}
?>